/**
 *  svhttpd         A Secure & Vitual http server
 *
 *  Copyright (c)   2007-2008 Dean To <svhttpd@gmail.com>
 *                  All Rights Reserved
 *  
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  Filename        : io.c
 *  Version         :
 *  Created         :
 *  Last Modified   :
 *
 *  HISTORY:
 *
 */

#include "misc.h"

int
setnoblock(int fd)
{
    int flag, ret;

    flag = fcntl(fd, F_GETFL);
    if (flag == -1)
        return -1;
    ret = fcntl(fd, F_SETFL, flag | O_NONBLOCK);
    if (ret == -1)
        return -1;
    return 0;
}

/**
 *  return value:
 *      >0, read bytes;
 *      0,  EOF;
 *      -1, error;
 *      -2, timeout;
 */
ssize_t 
readt(int fd, void *buf, size_t count, int *timeout)
{
    int ret;
    struct pollfd pfd;
    struct timeval begin_tv, cur_tv;

    if (*timeout != -1) {
        ret = gettimeofday(&begin_tv, NULL);
        if (ret == -1)
            return -1;
    }

    while (1) {
        ret = read(fd, buf, count);
        if (ret == -1) {
            if (errno == EAGAIN) {
                /* revise timeout */
                if (*timeout != -1) {
                    ret = gettimeofday(&cur_tv, NULL);
                    if (ret == -1)
                        return -1;
                    *timeout -= (cur_tv.tv_sec - begin_tv.tv_sec) * 1000 + 
                                (cur_tv.tv_usec - begin_tv.tv_usec) / 1000;
                    if (*timeout <= 0) {
                        /* timeout */
                        *timeout = 0;
                        return -2;
                    }
                }

                pfd.fd = fd;
                pfd.events = POLLIN;
                /* poll */
                ret = poll(&pfd, 1, *timeout);
                if (ret == -1) {
                    if (errno == EINTR)
                        continue;
                    return -1;
                } else if (ret == 0) {
                    /* timeout */
                    return -2;
                } else {
                    continue;
                }
            } else {
                /* other error */
                return -1;
            }
        } else if (ret == 0) {
            /* EOF */
            return 0;
        } else {
            /* ret > 0, OK */
            return ret;
        }
    }
}

/**
 *  return value:
 *      >=0, writen bytes;
 *      -1, error;
 *      -2, timeout;
 */
ssize_t 
writet(int fd, const void *buf, size_t count, int *timeout)
{
    int ret;
    struct pollfd pfd;
    struct timeval begin_tv, cur_tv;

    if (*timeout != -1) {
        ret = gettimeofday(&begin_tv, NULL);
        if (ret == -1)
            return -1;
    }

    while (1) {
        ret = write(fd, buf, count);
        if (ret == -1) {
            if (errno == EAGAIN) {
                /* revise timeout */
                if (*timeout != -1) {
                    ret = gettimeofday(&cur_tv, NULL);
                    if (ret == -1)
                        return -1;
                    *timeout -= (cur_tv.tv_sec - begin_tv.tv_sec) * 1000 + 
                                (cur_tv.tv_usec - begin_tv.tv_usec) / 1000;
                    if (*timeout <= 0) {
                        /* timeout */
                        *timeout = 0;
                        return -2;
                    }
                }

                pfd.fd = fd;
                pfd.events = POLLOUT;
                /* poll */
                ret = poll(&pfd, 1, *timeout);
                if (ret == -1) {
                    if (errno == EINTR)
                        continue;
                    return -1;
                } else if (ret == 0) {
                    /* timeout */
                    return -2;
                } else {
                    continue;
                }
            } else {
                /* other error */
                return -1;
            }
        } else {
            /* ret >= 0, OK */
            return ret;
        }
    }
}

/**
 *  return value:
 *      >0, ok, read bytes;
 *      0,  EOF;
 *      -1, error;
 *      -2, timeout;
 */
ssize_t
readnt(int fd, void *buf, size_t *count, int timeout, int timeout_total)
{
    int ret, to, to_bak;
    size_t read_idx;

    read_idx = 0;
    while (1) {
        if (timeout_total == -1) {
            /* infinite timeout_total */
            to = timeout;
        } else {
            if (timeout == -1) {
                to = timeout_total;
            } else {
                to = timeout < timeout_total ? timeout : timeout_total;
            }
        }

        to_bak = to;
        /* read */
        ret = readt(fd, buf + read_idx, *count - read_idx, &to);
        if (ret == -2) {
            /* timeout */
            *count = read_idx;
            return -2;
        } else if (ret == -1) {
            /* error */
            *count = read_idx;
            return -1;
        } else if (ret == 0) {
            /* EOF */
            *count = read_idx;
            return 0;
        } else {
            /* ret > 0 */
            read_idx += ret;
            /* receive all? */
            if (read_idx == *count)
                return read_idx;
        }

        /* revise timeout */
        if (timeout_total != -1) {
            timeout_total -= (to_bak - to);
            if (timeout_total <= 0) {
                /* timeout */
                *count = read_idx;
                return -2;
            }
        }
    }
}

/**
 *  return value:
 *      >=0, writen bytes;
 *      -1, error;
 *      -2, timeout;
 */
ssize_t
writent(int fd, const void *buf, size_t *count, int timeout, int timeout_total)
{
    int ret, to, to_bak;
    size_t write_idx;

    write_idx = 0;
    while (1) {
        if (timeout_total == -1) {
            /* infinite timeout_total */
            to = timeout;
        } else {
            if (timeout == -1) {
                to = timeout_total;
            } else {
                to = timeout < timeout_total ? timeout : timeout_total;
            }
        }

        to_bak = to;
        /* write */
        ret = writet(fd, buf + write_idx, *count - write_idx, &to);
        if (ret == -2) {
            /* timeout */
            *count = write_idx;
            return -2;
        } else if (ret == -1) {
            /* error */
            *count = write_idx;
            return -1;
        } else {
            /* ret >= 0 */
            write_idx += ret;
            /* send all? */
            if (write_idx == *count)
                return write_idx;
        }

        /* revise timeout */
        if (timeout_total != -1) {
            timeout_total -= (to_bak - to);
            if (timeout_total <= 0) {
                /* timeout */
                *count = write_idx;
                return -2;
            }
        }
    }
}
